"""
工作流引擎
负责工作流的执行、暂停、恢复等核心逻辑
"""
import json
import time
import re
import uuid
import os
import httpx
import yaml
from typing import Dict, Any, Optional, Tuple, List
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from jsonpath_ng import parse as jsonpath_parse
from jsonpath_ng.exceptions import JsonPathParserError
from models.experiment_template import ExperimentTemplate
from models.config_presets import Application

from models.workflow import Workflow
from models.workflow_instance import WorkflowInstance
from models.workflow_node_execution import WorkflowNodeExecution
from models.workflow_log import WorkflowLog
from models.experiment_template import ExperimentTemplate

env_FORBIDDEN_VISIT_ROMA = os.getenv("FORBIDDEN_VISIT_ROMA", "false").lower() == "true"

class WorkflowEngine:
    """工作流引擎"""

    # 工作流状态枚举
    STATUS_NOT_STARTED = "未运行"  # 新增：实例已创建但未启动
    STATUS_INIT = "初始化"
    STATUS_RUNNING = "运行中"
    STATUS_PAUSING = "暂停中"
    STATUS_PAUSED = "已暂停"
    STATUS_WAITING = "等待中"
    STATUS_COMPLETED = "已完成"
    STATUS_FAILED = "失败"
    STATUS_CANCELLED = "已取消"

    # 节点状态枚举
    NODE_STATUS_PREPARE = "准备中"
    NODE_STATUS_PENDING = "待执行"
    NODE_STATUS_SUBMITTED = "已提交"    # 已提交到外部系统，等待轮询
    NODE_STATUS_RUNNING = "运行中"      # 外部系统确认任务运行中
    NODE_STATUS_COMPLETED = "已完成"
    NODE_STATUS_FAILED = "失败"
    NODE_STATUS_SKIPPED = "已跳过"

    def __init__(self, db: Session):
        """初始化工作流引擎"""
        self.db = db

    def start_workflow(
        self,
        workflow_id: int,
        instance_name: str,
        trigger_type: str,
        input_params: Optional[Dict[str, Any]] = None,
        created_by: Optional[str] = None
    ) -> Tuple[bool, str, Optional[WorkflowInstance]]:
        """
        启动工作流（异步非阻塞）

        变更：
        1. 只创建实例，不执行工作流
        2. 立即返回实例ID
        3. 后台调度器会自动检测并执行

        Args:
            workflow_id: 工作流ID
            instance_name: 实例名称
            trigger_type: 触发类型 (manual/cron)
            input_params: 输入参数
            created_by: 创建人

        Returns:
            (是否成功, 错误信息, 工作流实例)
        """
        # 查询工作流
        workflow = self.db.query(Workflow).filter(Workflow.id == workflow_id).first()
        if not workflow:
            return False, "工作流不存在", None

        # 创建工作流实例（状态：init）
        instance = WorkflowInstance(
            workflow_id=workflow_id,
            instance_name=instance_name,
            status=self.STATUS_INIT,  # 初始化状态
            trigger_type=trigger_type,
            input_params=json.dumps(input_params or {}),
            created_by=created_by
        )
        self.db.add(instance)
        self.db.commit()
        self.db.refresh(instance)

        # 记录日志
        self._log(instance.id, "INFO", f"工作流实例创建成功，等待调度执行")

        # 🔥 关键变更：不再同步执行，由后台调度器处理
        return True, "", instance

    def _execute_workflow(self, instance: WorkflowInstance, workflow: Workflow):
        """
        执行工作流

        Args:
            instance: 工作流实例
            workflow: 工作流定义
        """
        try:
            # 更新实例状态为运行中
            instance.status = self.STATUS_RUNNING
            instance.started_at = datetime.now()
            self.db.commit()

            self._log(instance.id, "INFO", "工作流开始执行")

            # 解析工作流定义
            workflow_def = json.loads(workflow.workflow_definition)
            nodes = workflow_def.get("nodes", [])

            if not nodes:
                raise Exception("工作流没有定义任何节点")

            # 从第一个节点开始执行
            current_node_id = nodes[0].get("id")
            node_outputs = {}  # 存储节点输出,用于变量引用

            while current_node_id:
                # 检查是否暂停
                self.db.refresh(instance)
                if instance.status == self.STATUS_PAUSING:
                    instance.status = self.STATUS_PAUSED
                    instance.current_node_id = current_node_id
                    self.db.commit()
                    self._log(instance.id, "INFO", f"工作流已暂停,当前节点: {current_node_id}")
                    return

                # 检查是否取消
                if instance.status == self.STATUS_CANCELLED:
                    self._log(instance.id, "INFO", "工作流已取消")
                    return

                # 执行当前节点
                node = self._find_node(nodes, current_node_id)
                if not node:
                    raise Exception(f"找不到节点: {current_node_id}")

                # 替换节点配置中的变量
                node_config = self._replace_variables(
                    node.get("config", {}),
                    instance,
                    node_outputs
                )

                success, output, error_msg = self._execute_node(
                    instance.id,
                    node,
                    node_config
                )

                if not success:
                    # 节点执行失败
                    instance.status = self.STATUS_FAILED
                    instance.finished_at = datetime.now()
                    instance.error_message = error_msg
                    self.db.commit()
                    self._log(instance.id, "ERROR", f"工作流执行失败: {error_msg}")
                    return

                # 保存节点输出
                node_outputs[current_node_id] = output

                # 获取下一个节点
                current_node_id = node.get("next")

            # 所有节点执行完成
            instance.status = self.STATUS_COMPLETED
            instance.finished_at = datetime.now()
            instance.duration = int((instance.finished_at - instance.started_at).total_seconds())
            self.db.commit()

            self._log(instance.id, "INFO", "工作流执行完成")

        except Exception as e:
            # 工作流执行异常
            instance.status = self.STATUS_FAILED
            instance.finished_at = datetime.now()
            instance.error_message = str(e)
            self.db.commit()
            self._log(instance.id, "ERROR", f"工作流执行异常: {str(e)}")

    def _execute_node(
        self,
        instance_id: int,
        node: Dict[str, Any],
        node_config: Dict[str, Any]
    ) -> Tuple[bool, Dict[str, Any], str]:
        """
        执行节点

        Args:
            instance_id: 工作流实例ID
            node: 节点定义
            node_config: 节点配置(已替换变量)

        Returns:
            (是否成功, 输出数据, 错误信息)
        """
        node_id = node.get("id")
        node_name = node.get("name")
        node_type = node.get("type")

        # 创建节点执行记录
        execution = WorkflowNodeExecution(
            workflow_instance_id=instance_id,
            node_id=node_id,
            node_name=node_name,
            node_type=node_type,
            status=self.NODE_STATUS_RUNNING,
            input_data=json.dumps(node_config),
            started_at=datetime.now()
        )
        self.db.add(execution)
        self.db.commit()
        self.db.refresh(execution)

        self._log(instance_id, "INFO", f"节点 {node_name} 开始执行", execution.id)

        try:
            # 根据节点类型执行不同的逻辑
            if node_type == "create_experiment":
                output = self._execute_create_experiment(node_config)
            elif node_type == "download_data":
                output = self._execute_download_data(node_config)
            elif node_type == "data_processing":
                output = self._execute_data_processing(node_config)
            elif node_type == "condition":
                output = self._execute_condition(node_config)
            else:
                raise Exception(f"不支持的节点类型: {node_type}")

            # 节点执行成功
            execution.status = self.NODE_STATUS_COMPLETED
            execution.output_data = json.dumps(output)
            execution.finished_at = datetime.now()
            execution.duration = int((execution.finished_at - execution.started_at).total_seconds())
            self.db.commit()
            self._log(instance_id, "INFO", f"节点 {node_name} 执行成功", execution.id)

            return True, output, ""

        except Exception as e:
            # 节点执行失败
            execution.status = self.NODE_STATUS_FAILED
            execution.error_message = str(e)
            execution.finished_at = datetime.now()
            execution.duration = int((execution.finished_at - execution.started_at).total_seconds())
            self.db.commit()

            self._log(instance_id, "ERROR", f"节点 {node_name} 执行失败: {str(e)}", execution.id)

            return False, {}, str(e)

    def _execute_create_experiment(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        执行创建实验节点

        Args:
            config: 节点配置

        Returns:
            输出数据
        """
        # TODO: 实际调用创建实验的API
        # 这里模拟执行
        time.sleep(1)  # 模拟耗时
        return {
            "experimentId": "exp_123456",
            "status": "已完成"
        }

    def _execute_download_data(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        执行下载数据节点

        Args:
            config: 节点配置

        Returns:
            输出数据
        """
        # TODO: 实际执行数据下载逻辑
        time.sleep(1)  # 模拟耗时
        return {
            "filePath": config.get("target_path", "/data/output"),
            "fileSize": 1024000,
            "status": "下载完成"
        }

    def _execute_data_processing(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        执行数据处理节点

        Args:
            config: 节点配置

        Returns:
            输出数据
        """
        # TODO: 实际调用API进行数据处理
        time.sleep(1)  # 模拟耗时
        return {
            "status": "处理完成",
            "recordCount": 10000
        }

    def _execute_condition(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        执行条件判断节点

        Args:
            config: 节点配置

        Returns:
            输出数据
        """
        condition = config.get("condition", "")
        operator = config.get("operator", "==")

        # 简单的条件评估实现
        # 在实际应用中应该使用更安全的方式
        try:
            # 解析条件表达式
            # 格式: {{variable}} operator value
            # 例如: {{experiment.status}} == "completed"

            # 这里简化处理,返回True以继续流程
            # 实际应该根据实际变量值进行判断
            result = True

            return {
                "condition_result": result,
                "evaluated_condition": condition,
                "operator": operator
            }
        except Exception as e:
            raise Exception(f"条件判断失败: {str(e)}")

    def _replace_variables(
        self,
        config: Dict[str, Any],
        instance: WorkflowInstance,
        node_outputs: Dict[str, Dict[str, Any]]
    ) -> Dict[str, Any]:
        """
        增强版变量替换，支持 JSONPath 表达式

        支持格式：
        - {{workflow.name}} - 内置变量
        - {{nodeId.field}} - 简单字段访问（向后兼容）
        - {{nodeId.jsonpath.expression}} - JSONPath 表达式

        示例：
        - {{nodeA.versionInfo.outputs[0].obsUrl}}
        - {{nodeB.versionInfo.metrics.accuracy}}
        - {{nodeC.versionInfo.outputs[?(@.type=='result')].obsUrl}}

        Args:
            config: 原始配置
            instance: 工作流实例
            node_outputs: 节点输出集合

        Returns:
            替换后的配置
        """
        config_str = json.dumps(config)

        # 1. 替换内置变量
        config_str = config_str.replace("{{workflow.name}}", instance.instance_name)
        config_str = config_str.replace("{{workflow.id}}", str(instance.id))
        config_str = config_str.replace("{{timestamp}}", str(int(time.time())))

        # 2. 替换节点输出变量（支持 JSONPath）
        # 匹配格式: {{nodeId.path.to.field}} 或 {{nodeId.output.field}} (向后兼容)
        pattern = r'\{\{([^.}]+)\.([^}]+)\}\}'
        matches = re.findall(pattern, config_str)

        for node_id, path_expr in matches:
            # 跳过已处理的内置变量
            if node_id in ["workflow", "timestamp"]:
                continue

            if node_id not in node_outputs:
                self._log(instance.id, "WARNING",
                         f"引用的节点 '{node_id}' 未找到或未执行完成")
                continue

            try:
                # 保存原始路径表达式用于构建变量字符串
                original_path_expr = path_expr

                # 向后兼容旧格式 {{nodeId.output.field}}
                if path_expr.startswith("output."):
                    # 移除 "output." 前缀
                    path_expr = path_expr[7:]

                # 使用 JSONPath 提取值
                value = self._extract_value_by_jsonpath(
                    node_outputs[node_id],
                    path_expr
                )

                if value is not None:
                    # 构建原始变量引用字符串（使用原始路径表达式）
                    original_var = f"{{{{{node_id}.{original_path_expr}}}}}"

                    # 替换变量
                    if isinstance(value, (dict, list)):
                        replacement = json.dumps(value)
                    else:
                        replacement = str(value)

                    config_str = config_str.replace(original_var, replacement)

                    self._log(instance.id, "DEBUG",
                             f"成功替换变量: {original_var} = {value}")
                else:
                    self._log(instance.id, "WARNING",
                             f"变量路径未找到值: {{{{{node_id}.{original_path_expr}}}}}")

            except Exception as e:
                self._log(instance.id, "ERROR",
                         f"变量替换失败: {{{{{node_id}.{path_expr}}}}} - {str(e)}")
        return json.loads(config_str)

    def _replace_variables_in_string(
        self,
        content: str,
        instance: WorkflowInstance,
        node_outputs: Dict[str, Dict[str, Any]]
    ) -> str:
        """
        替换字符串中的变量（用于 YAML 配置等多行文本）

        支持格式：
        - {{workflow.name}} - 内置变量
        - {{nodeId.field}} - 简单字段访问
        - {{nodeId.jsonpath.expression}} - JSONPath 表达式

        Args:
            content: 原始字符串内容
            instance: 工作流实例
            node_outputs: 节点输出集合

        Returns:
            替换后的字符串
        """
        # 1. 替换内置变量
        content = content.replace("{{workflow.name}}", instance.instance_name)
        content = content.replace("{{workflow.id}}", str(instance.id))
        content = content.replace("{{timestamp}}", str(int(time.time())))

        # 2. 替换节点输出变量（支持 JSONPath）
        pattern = r'\{\{([^.}]+)\.([^}]+)\}\}'
        matches = re.findall(pattern, content)

        for node_id, path_expr in matches:
            # 跳过已处理的内置变量
            if node_id in ["workflow", "timestamp"]:
                continue

            if node_id not in node_outputs:
                self._log(instance.id, "WARNING",
                         f"引用的节点 '{node_id}' 未找到或未执行完成")
                continue

            try:
                # 保存原始路径表达式
                original_path_expr = path_expr

                # 向后兼容旧格式 {{nodeId.output.field}}
                if path_expr.startswith("output."):
                    path_expr = path_expr[7:]

                # 使用 JSONPath 提取值
                value = self._extract_value_by_jsonpath(
                    node_outputs[node_id],
                    path_expr
                )

                if value is not None:
                    # 构建原始变量引用字符串
                    original_var = f"{{{{" + node_id + "." + original_path_expr + "}}}}"

                    # 替换变量
                    if isinstance(value, (dict, list)):
                        replacement = json.dumps(value)
                    else:
                        replacement = str(value)

                    content = content.replace(original_var, replacement)

                    self._log(instance.id, "DEBUG",
                             f"成功替换变量: {original_var} = {value}")
                else:
                    self._log(instance.id, "WARNING",
                             f"变量路径未找到值: {{{{{node_id}.{original_path_expr}}}}}")

            except Exception as e:
                self._log(instance.id, "ERROR",
                         f"变量替换失败: {{{{{node_id}.{path_expr}}}}} - {str(e)}")

        return content

    def _extract_value_by_jsonpath(self, data: Dict[str, Any], path_expr: str) -> Any:
        """
        使用 JSONPath 从数据中提取值

        Args:
            data: 节点输出数据（JSON对象）
            path_expr: JSONPath 表达式，如 "versionInfo.outputs[0].obsUrl"

        Returns:
            提取的值，如果路径不存在返回 None

        示例：
            data = {
                "versionInfo": {
                    "outputs": [
                        {"obsUrl": "obs://path/to/data", "type": "result"}
                    ]
                }
            }

            path_expr = "versionInfo.outputs[0].obsUrl"
            返回: "obs://path/to/data"
        """
        try:
            # 简单字段直接访问（性能优化）
            if '.' not in path_expr and '[' not in path_expr:
                return data.get(path_expr)

            # 将简化路径转换为标准 JSONPath
            # "versionInfo.outputs[0].obsUrl" -> "$.versionInfo.outputs[0].obsUrl"
            if not path_expr.startswith('$'):
                path_expr = f"$.{path_expr}"

            # 解析并执行 JSONPath
            jsonpath_expr = jsonpath_parse(path_expr)
            matches = jsonpath_expr.find(data)

            if matches:
                # 如果只有一个匹配结果，直接返回值
                if len(matches) == 1:
                    return matches[0].value
                # 多个匹配结果，返回列表
                else:
                    return [match.value for match in matches]
            else:
                return None

        except JsonPathParserError as e:
            raise ValueError(f"JSONPath 语法错误: {path_expr} - {str(e)}")
        except Exception as e:
            raise ValueError(f"提取值失败: {str(e)}")

    def _find_node(self, nodes: list, node_id: str) -> Optional[Dict[str, Any]]:
        """查找节点"""
        for node in nodes:
            if node.get("id") == node_id:
                return node
        return None

    def _log(
        self,
        instance_id: int,
        level: str,
        message: str,
        node_execution_id: Optional[int] = None
    ):
        """
        记录日志

        Args:
            instance_id: 工作流实例ID
            level: 日志级别
            message: 日志消息
            node_execution_id: 节点执行ID(可选)
        """
        log = WorkflowLog(
            workflow_instance_id=instance_id,
            node_execution_id=node_execution_id,
            log_level=level,
            log_message=message
        )
        self.db.add(log)
        self.db.commit()

    def pause_workflow(self, instance_id: int) -> Tuple[bool, str]:
        """
        暂停工作流

        Args:
            instance_id: 工作流实例ID

        Returns:
            (是否成功, 错误信息)
        """
        instance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()

        if not instance:
            return False, "工作流实例不存在"

        if instance.status != self.STATUS_RUNNING:
            return False, f"工作流状态为 {instance.status},无法暂停"

        # 设置暂停标志
        instance.status = self.STATUS_PAUSED
        self.db.commit()

        self._log(instance_id, "INFO", "收到暂停请求")

        return True, ""

    def resume_workflow(self, instance_id: int) -> Tuple[bool, str]:
        """
        恢复工作流（异步非阻塞）

        变更：
        1. 只更新状态为 running
        2. 调度器会自动检测并继续执行

        Args:
            instance_id: 工作流实例ID

        Returns:
            (是否成功, 错误信息)
        """
        instance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()

        if not instance:
            return False, "工作流实例不存在"

        if instance.status != self.STATUS_PAUSED:
            return False, f"工作流状态为 {instance.status},无法恢复"

        # 🔥 只更新状态，不执行工作流
        instance.status = self.STATUS_RUNNING
        self.db.commit()

        self._log(instance_id, "INFO", "工作流恢复执行，等待调度器处理")

        return True, ""

    def retry_workflow(
        self,
        instance_id: int,
        from_node_id: Optional[str] = None
    ) -> Tuple[bool, str]:
        """
        重试工作流

        Args:
            instance_id: 工作流实例ID
            from_node_id: 从哪个节点开始重试(可选)

        Returns:
            (是否成功, 错误信息)
        """
        instance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()

        if not instance:
            return False, "工作流实例不存在"

        if instance.status not in [self.STATUS_FAILED]:
            return False, f"工作流状态为 {instance.status},无法重试"

        # TODO: 实现从指定节点重试的逻辑
        # 这里简化为重新执行整个工作流

        workflow = self.db.query(Workflow).filter(
            Workflow.id == instance.workflow_id
        ).first()

        if not workflow:
            return False, "工作流不存在"

        # 重置实例状态
        instance.status = self.STATUS_INIT
        instance.error_message = None
        self.db.commit()

        self._log(instance_id, "INFO", "重试工作流执行")
        self._execute_workflow(instance, workflow)

        return True, ""

    def cancel_workflow(self, instance_id: int) -> Tuple[bool, str]:
        """
        取消工作流

        Args:
            instance_id: 工作流实例ID

        Returns:
            (是否成功, 错误信息)
        """
        instance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()

        if not instance:
            return False, "工作流实例不存在"

        if instance.status in [self.STATUS_COMPLETED, self.STATUS_FAILED, self.STATUS_CANCELLED]:
            return False, f"工作流已结束,状态为 {instance.status}"

        # 取消工作流
        instance.status = self.STATUS_CANCELLED
        instance.finished_at = datetime.now()
        self.db.commit()

        self._log(instance_id, "INFO", "工作流已取消")

        return True, ""

    # ==================== 异步执行核心方法 ====================

    def execute_workflow_step(self, instance_id: int) -> bool:
        """
        执行工作流的单步操作（由调度器定时调用）

        返回：
            bool: 是否需要继续调度（False表示工作流已结束或暂停）
        """
        instance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()

        if not instance:
            return False

        # 检查工作流状态
        if instance.status == self.STATUS_PAUSING:
            # 收到暂停请求，检查当前节点是否完成
            current_execution = self._get_current_node_execution(instance_id)
            if current_execution and current_execution.status in [
                self.NODE_STATUS_COMPLETED,
                self.NODE_STATUS_FAILED,
                self.NODE_STATUS_SKIPPED
            ]:
                # 当前节点已完成，可以暂停
                instance.status = self.STATUS_PAUSED
                self.db.commit()
                self._log(instance_id, "INFO", "工作流已暂停")
                return False

        if instance.status in [self.STATUS_PAUSED, self.STATUS_CANCELLED,
                               self.STATUS_COMPLETED, self.STATUS_FAILED]:
            # 工作流已结束，不需要继续调度
            return False

        # 初始化状态 → 运行中
        if instance.status == self.STATUS_INIT:
            instance.status = self.STATUS_RUNNING
            instance.started_at = datetime.now()
            self.db.commit()
            self._log(instance_id, "INFO", "工作流开始执行")

        # 执行当前节点
        try:
            # 使用实例快照，兼容旧版本
            if instance.workflow_definition_snapshot:
                # 新版本：使用实例快照
                workflow_def = json.loads(instance.workflow_definition_snapshot)
            else:
                # 旧版本：从工作流/模板读取定义
                workflow = self.db.query(Workflow).filter(
                    Workflow.id == instance.workflow_id
                ).first()
                if not workflow:
                    # 尝试从模板读取
                    from models.workflow_template import WorkflowTemplate
                    template = self.db.query(WorkflowTemplate).filter(
                        WorkflowTemplate.id == instance.template_id
                    ).first()
                    if template:
                        workflow_def = json.loads(template.workflow_definition)
                    else:
                        raise Exception("无法找到工作流定义")
                else:
                    workflow_def = json.loads(workflow.workflow_definition)

            nodes = workflow_def.get("nodes", [])

            # 获取当前应该执行的节点
            current_node_id = self._get_next_pending_node(instance_id, nodes)

            if not current_node_id:
                # 没有待执行的节点，检查是否全部完成
                if self._all_nodes_completed(instance_id):
                    instance.status = self.STATUS_COMPLETED
                    instance.finished_at = datetime.now()
                    instance.duration = int((instance.finished_at - instance.started_at).total_seconds())
                    self.db.commit()
                    self._log(instance_id, "INFO", "工作流执行完成")
                    return False
                else:
                    # 还有节点在执行中（submitted/running），等待轮询结果
                    instance.status = self.STATUS_WAITING
                    self.db.commit()
                    return True  # 继续调度

            # 提交当前节点任务
            node = self._find_node(nodes, current_node_id)
            node_config = self._replace_variables(
                node.get("config", {}),
                instance,
                self._get_node_outputs(instance_id)
            )

            success, error_msg = self._submit_node_task(
                instance_id,
                node,
                node_config
            )

            if not success:
                instance.status = self.STATUS_FAILED
                instance.finished_at = datetime.now()
                instance.error_message = error_msg
                self.db.commit()
                self._log(instance_id, "ERROR", f"节点提交失败: {error_msg}")
                return False

            return True  # 继续调度

        except Exception as e:
            instance.status = self.STATUS_FAILED
            instance.finished_at = datetime.now()
            instance.error_message = str(e)
            
            self.db.commit()
            self._log(instance_id, "ERROR", f"工作流执行异常: {str(e)}")
            return False

    def _submit_node_task(
        self,
        instance_id: int,
        node: Dict[str, Any],
        node_config: Dict[str, Any]
    ) -> Tuple[bool, str]:
        """
        提交节点任务到外部系统（非阻塞）

        返回：
            (是否成功, 错误信息)
        """
        node_id = node.get("id")
        node_name = node.get("name")
        node_type = node.get("type")

        keep, reason = self._pre_submit_node_task(instance_id=instance_id, node=node, node_config=node_config)
        if not keep:
            print(f"跳过提交节点任务: {node_name}, 原因: {reason}")
            return True, ""
        
        # 创建节点执行记录
        execution = self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.workflow_instance_id == instance_id,
            WorkflowNodeExecution.status == self.NODE_STATUS_PENDING,
            WorkflowNodeExecution.node_id == node_id,
        ).first()

        if not execution:
            execution = WorkflowNodeExecution(
                workflow_instance_id=instance_id,
                node_id=node_id,
                node_name=node_name,
                node_type=node_type,
                status=self.NODE_STATUS_PENDING,
                input_data=json.dumps(node_config),
                created_at=datetime.now()
            )
            self.db.add(execution)
            self.db.commit()
            self.db.refresh(execution)

        self._log(instance_id, "INFO", f"开始提交节点任务: {node_name}", execution.id)

        try:
            # 根据节点类型调用不同的提交方法
            if node_type == "create_experiment":
                if env_FORBIDDEN_VISIT_ROMA:
                    external_task_id = "1234567890"  # 模拟任务ID
                    print("当前环境禁止访问Roma服务，已模拟创建实验任务ID")
                else:
                    external_task_id = self._submit_create_experiment(instance_id, node_config)
            elif node_type == "download_data":
                external_task_id = self._submit_download_data(node_config)
            elif node_type == "data_processing":
                external_task_id = self._submit_data_processing(node_config)
            elif node_type == "condition":
                # 条件节点立即执行，不需要提交
                result = self._execute_condition(node_config)
                execution.status = self.NODE_STATUS_COMPLETED
                execution.output_data = json.dumps(result)
                execution.finished_at = datetime.now()
                execution.duration = 0
                self.db.commit()
                return True, ""
            else:
                raise Exception(f"不支持的节点类型: {node_type}")

            # 更新节点状态为 submitted
            execution.status = self.NODE_STATUS_SUBMITTED
            execution.external_task_id = external_task_id
            execution.submitted_at = datetime.now()
            execution.next_poll_at = datetime.now() + timedelta(seconds=execution.poll_interval)
            self.db.commit()

            self._log(instance_id, "INFO",
                     f"节点任务提交成功: {node_name}, 外部任务ID: {external_task_id}",
                     execution.id)

            return True, ""

        except Exception as e:
            execution.status = self.NODE_STATUS_FAILED
            execution.error_message = str(e)
            execution.finished_at = datetime.now()
            self.db.commit()
            self._log(instance_id, "ERROR", f"节点任务提交失败: {str(e)}", execution.id)
            return False, str(e)

    def poll_node_status(self, execution_id: int) -> bool:
        """
        轮询节点执行状态（由调度器定时调用）

        返回：
            bool: 是否需要继续轮询
        """
        execution = self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.id == execution_id
        ).first()

        if not execution:
            return False

        # 只轮询 submitted 或 running 状态的节点
        if execution.status not in [self.NODE_STATUS_SUBMITTED, self.NODE_STATUS_RUNNING]:
            return False

        # 检查是否到达轮询时间
        if execution.next_poll_at and datetime.now() < execution.next_poll_at:
            return True  # 还未到轮询时间，继续等待

        try:
            # 调用外部API查询任务状态
            external_status, output_data, error_msg = self._query_external_task_status(
                execution.node_type,
                execution.external_task_id
            )

            # 更新轮询计数
            execution.poll_count += 1

            if external_status == "running":
                # 任务仍在运行中
                execution.status = self.NODE_STATUS_RUNNING
                execution.next_poll_at = datetime.now() + timedelta(seconds=execution.poll_interval)
                self.db.commit()

                self._log(execution.workflow_instance_id, "INFO",
                         f"节点 {execution.node_name} 仍在运行中 (轮询次数: {execution.poll_count})",
                         execution.id)
                return True  # 继续轮询

            elif external_status == "completed":
                # 任务完成
                execution.status = self.NODE_STATUS_COMPLETED
                execution.output_data = json.dumps(output_data)
                execution.finished_at = datetime.now()
                execution.duration = int((execution.finished_at - execution.started_at).total_seconds())
                self.db.commit()

                self._log(execution.workflow_instance_id, "INFO",
                         f"节点 {execution.node_name} 执行成功",
                         execution.id)

                # 触发工作流继续执行下一个节点
                instance = self.db.query(WorkflowInstance).filter(
                    WorkflowInstance.id == execution.workflow_instance_id
                ).first()
                if instance and instance.status == self.STATUS_WAITING:
                    instance.status = self.STATUS_RUNNING
                    self.db.commit()

                return False  # 停止轮询

            elif external_status == "failed":
                # 任务失败
                execution.status = self.NODE_STATUS_FAILED
                execution.error_message = error_msg
                execution.finished_at = datetime.now()
                execution.duration = int((execution.finished_at - execution.started_at).total_seconds())
                self.db.commit()

                self._log(execution.workflow_instance_id, "ERROR",
                         f"节点 {execution.node_name} 执行失败: {error_msg}",
                         execution.id)

                # 标记工作流失败
                instance = self.db.query(WorkflowInstance).filter(
                    WorkflowInstance.id == execution.workflow_instance_id
                ).first()
                if instance:
                    instance.status = self.STATUS_FAILED
                    instance.finished_at = datetime.now()
                    instance.error_message = f"节点 {execution.node_name} 执行失败"
                    self.db.commit()

                return False  # 停止轮询

            else:
                # 未知状态
                self._log(execution.workflow_instance_id, "WARNING",
                         f"节点 {execution.node_name} 返回未知状态: {external_status}",
                         execution.id)
                execution.next_poll_at = datetime.now() + timedelta(seconds=execution.poll_interval)
                self.db.commit()
                return True  # 继续轮询

        except Exception as e:
            # 轮询异常，记录日志但继续轮询
            self._log(execution.workflow_instance_id, "WARNING",
                     f"节点状态轮询异常: {str(e)}",
                     execution.id)
            execution.next_poll_at = datetime.now() + timedelta(seconds=execution.poll_interval)
            self.db.commit()
            return True  # 继续轮询

    # ==================== 辅助方法 ====================

    def _reuse_or_fetch_token(self, account_name: str, account_secret: str) -> str:
        """
        复用或获取 ROMA API Token

        Args:
            account_name: ROMA 账号名
            account_secret: ROMA 账号密钥

        Returns:
            str: Access Token
        """
        CACHED_TOKEN_PATH = "cached_roma_token.token"
        TOKEN_EXPIRE_DELTA = timedelta(hours=23, minutes=50)

        now = datetime.now()

        # 检查缓存的 token
        if os.path.exists(CACHED_TOKEN_PATH):
            try:
                with open(CACHED_TOKEN_PATH, "r", encoding="utf-8") as f:
                    cached = json.load(f)
                    ts = cached.get("timestamp")
                    if ts:
                        ts_dt = datetime.fromisoformat(ts)
                        if now - ts_dt < TOKEN_EXPIRE_DELTA and "access_token" in cached:
                            return cached["access_token"]
            except Exception as e:
                self._log(0, "WARNING", f"读取缓存 token 失败: {str(e)}")

        # 获取新 token
        url = "https://csb.roma.huawei.com/csb/v2/enterprise/tenant/rest/csbiam/auth/enterprise-token"
        headers = {"Content-Type": "application/json"}
        data = {
            "data": {
                "type": "JWT-Token",
                "attributes": {
                    "integrationAccountName": account_name,
                    "integrationAccountSecret": account_secret,
                },
            }
        }

        with httpx.Client(verify=False) as client:
            response = client.post(url, headers=headers, json=data)
            response.raise_for_status()
            access_token = response.json()["accessToken"]

            # 缓存 token
            with open(CACHED_TOKEN_PATH, "w", encoding="utf-8") as f:
                cache_content = {
                    "timestamp": now.isoformat(),
                    "access_token": access_token,
                }
                json.dump(cache_content, f, ensure_ascii=False)
                f.write("\n")

            return access_token

    def _merge_params(self, base_config: list, user_params: Dict[str, Any]) ->  Dict[str, Any]:
        """
        合并基础参数和用户参数

        Args:
            base_config: 基础参数字典 {}
            user_params: 用户参数字典 {"key": "value"}

        Returns:
            list: 合并后的参数列表
        """

        # 更新或添加用户参数
        for key, value in (user_params or dict()).items():
            if key in base_config:
                base_config[key]= str(value)
        return base_config
    
    def _pre_submit_node_task(self,
        instance_id: int,
        node: Dict[str, Any],
        node_config: Dict[str, Any]) -> Tuple[bool, str]:
        
        node_type = node.get("type")
        if node_type != "create_experiment":
            return True, ""

        instance: WorkflowInstance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()
        if not instance:
            raise ValueError(f"工作流实例不存在: {instance_id}")
        node_outputs = self._get_node_outputs(instance_id)
        yaml_config = node_config.get("experimentConfig")
        if not yaml_config:
            return True, ""
        yaml_config_replaced = self._replace_variables_in_string(
            yaml_config,
            instance,
            node_outputs
        )
        template_config = yaml.safe_load(yaml_config_replaced)
        ext_info = template_config['config'].get('ext')
        input_pair = tuple()
        output_pair = tuple()
        if ext_info:
            input_info = ext_info.get('nas',dict()).get('input',dict())
            if input_info.get('enable'):
                input_pair = input_info.get('nas_id'), input_info.get('path')
            output_info = ext_info.get('nas',dict()).get('output',dict())
            if output_info.get('enable'):
                output_pair = output_info.get('nas_id'),output_info.get('path')
        
        # 如果没有使用 EFS，则跳过
        if not input_pair and not output_pair:
            return True, ""
        
        execution = self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.workflow_instance_id == instance_id,
            WorkflowNodeExecution.node_id == node.get("id"),
            # 检查所有活跃状态，防止重复提交
            WorkflowNodeExecution.status.in_([
                self.NODE_STATUS_PREPARE,
                self.NODE_STATUS_PENDING,
                self.NODE_STATUS_SUBMITTED,
                self.NODE_STATUS_RUNNING
            ]),
        ).first()
        
        # 提交 EFS 任务
        if not execution:
            self._submit_efs_task(input_pair, output_pair)
            execution = WorkflowNodeExecution(
                workflow_instance_id=instance_id,
                node_id=node.get("id"),
                node_name=node.get("name"),
                node_type=node_type,
                status=self.NODE_STATUS_PREPARE,
                input_data=json.dumps(node_config),
                started_at=datetime.now()
            )
            self.db.add(execution)
            self.db.commit()
            self.db.refresh(execution) 
            self._log(instance_id, "INFO", f"EFS 任务已提交，等待完成: {node.get('name')}", execution.id)

        # 检查 EFS 任务是否完成
        if execution.status == self.NODE_STATUS_PREPARE and self._check_efs_task(input_pair, output_pair):
            execution = self.db.query(WorkflowNodeExecution).filter(
                WorkflowNodeExecution.id == execution.id
            ).first()
            execution.status = self.NODE_STATUS_PENDING
            self.db.commit()
            return True, ""
        return False, "等待 EFS 任务完成"

    def _submit_efs_task(self, *args):
        """
        提交 EFS 任务
        """
        def submit_single_efs(pair):
            if not pair or len(pair) != 2:
                return
            nas_id, path = pair
            # TODO 这里实现 EFS 任务提交逻辑
            pass
        for pair in args:
            submit_single_efs(pair)

    def _check_efs_task(self, *args) -> bool:
        """
        检查 EFS 任务是否完成
        """
        def check_single_efs(pair) -> bool:
            if not pair or len(pair) != 2:
                return True  # 没有任务，视为已完成
            nas_id, path = pair
            # TODO 这里实现 EFS 任务状态检查逻辑

            return True 
        return all([check_single_efs(pair) for pair in args])
    # ==================== 外部任务提交方法 ====================

    def _submit_create_experiment(self, instance_id: int, config: Dict[str, Any]) -> str:
        """
        提交创建实验任务到 ROMA 平台

        Args:
            instance_id: 工作流实例ID
            config: 配置参数，包含:
                - experimentConfig: 完整的实验配置 YAML（优先使用）
                - template_id: 模板ID（当没有 experimentConfig 时使用）
                - experiment_name: 实验名称
                - params: 用户自定义参数 (Dict)

        Returns:
            str: ROMA 返回的 versionUid (外部任务ID)

        Raises:
            ValueError: 参数验证失败
            Exception: ROMA API 调用失败
        """
        # 获取工作流实例和节点输出（用于变量替换）
        instance: WorkflowInstance = self.db.query(WorkflowInstance).filter(
            WorkflowInstance.id == instance_id
        ).first()
        if not instance:
            raise ValueError(f"工作流实例不存在: {instance_id}")

        node_outputs = self._get_node_outputs(instance_id)
        template_id = config.get("template_id")
        if not template_id:
            raise ValueError("必须提供 template_id")
        # 从数据库获取实验模板
        template :ExperimentTemplate = self.db.query(ExperimentTemplate).filter(
            ExperimentTemplate.id == template_id,
            ExperimentTemplate.deleted_at.is_(None)
        ).first()

        if not template:
            raise ValueError(f"未找到实验模板 ID: {template_id}")        
        
        app: Application = self.db.query(Application).filter(
            Application.id == template.application_id,
        ).first()
        
        if not app:
            raise ValueError(f"未找到应用配置 ID: {template.application_id}")

        # 1. 优先使用 experimentConfig（完整的 YAML 配置）
        if config.get("experimentConfig"):
            try:
                yaml_config = config["experimentConfig"]

                # 替换 YAML 字符串中的变量
                yaml_config_replaced = self._replace_variables_in_string(
                    yaml_config,
                    instance,
                    node_outputs
                )

                # 解析 YAML 为字典
                template_config = yaml.safe_load(yaml_config_replaced)

                if not isinstance(template_config, dict):
                    raise ValueError("experimentConfig 解析后不是有效的配置对象")

                self._log(instance_id, "INFO",
                         f"使用节点配置的 experimentConfig (长度: {len(yaml_config)} 字符)")

            except yaml.YAMLError as e:
                raise ValueError(f"experimentConfig YAML 解析失败: {str(e)}")
            except Exception as e:
                raise ValueError(f"处理 experimentConfig 失败: {str(e)}")

        # 2. 否则使用传统方式：从模板加载并合并参数
        else:
            experiment_name = config.get("experiment_name")
            user_params = config.get("params", {})

            # 解析模板配置
            try:
                template_config = json.loads(template.json_config)
            except json.JSONDecodeError as e:
                raise ValueError(f"模板配置 JSON 解析失败: {str(e)}")

            # 合并用户参数到模板配置
            if user_params:
                if isinstance(user_params, str):
                    try:
                        user_params = json.loads(user_params)
                    except json.JSONDecodeError as e:
                        raise ValueError(f"用户配置解析失败: {str(e)}")
                template_config = self._merge_params(template_config, user_params)

            # 更新实验名称
            if not template_config.get("name") and experiment_name:
                template_config["name"] = experiment_name

            self._log(instance_id, "INFO",
                     f"使用模板 ID {template_id} + params 创建实验")

        template_config['name'] = instance.instance_name + '-' + config.get('experiment_name', '')
        template_config['userId'] = instance.trigger_user or instance.created_by

        account_name = os.getenv("ROMA_ACCOUNT_NAME", "BMC_API_USER")
        account_secret = os.getenv("ROMA_ACCOUNT_SECRET", "AESGCM:2856B514BE721213DBEADA0A5F37BD15:57AA692302F16DE42F5914D82F45B8F94EA1635A9D72912911C19BD748F63CD6E33C34BDB2AFD98DDF67A45F1A084DF2E7DED0C3")

        token = self._reuse_or_fetch_token(account_name, account_secret)

        url = "https://apigw.huawei.com/api/csb/roma-aistudio/train/job/create"
        headers = {
            "Content-Type": "application/json",
            "Authorization": token,
            "X-HW-ID": app.hw_id, #"com.huawei.ipd.noproduct.tenant_z00515901.BJ",
            "X-HW-APPKEY": app.hw_appkey, #"31y1UZJvij3YQ9QpZGk6dw==",
        }
        query_params = {
            "appid": app.appid,#"com.app.bmc.ai.aicase",
            "trainApiVersion": app.trainApiVersion, #"V2",
            "region": app.region, #"cn-southwest-2",
            "vendor": app.vender, #"HEC",
            # "TENANTSPACEID": "CSB",
        }
        # 8. 发送请求
        try:
            with httpx.Client(verify=False, timeout=30.0) as client:
                response = client.post(
                    url,
                    headers=headers,
                    params=query_params,
                    json=template_config
                )
                response.raise_for_status()
                result = response.json()

                # 9. 提取 versionUid 作为外部任务ID
                if not result.get("success"):
                    error_msg = result.get("msg", "未知错误")
                    raise Exception(f"ROMA API 返回失败: {error_msg}")

                version_uid = result.get("versionUid")
                if not version_uid:
                    raise Exception("ROMA API 未返回 versionUid")

                # 从配置中获取实验名称用于日志
                exp_name = template_config.get("name", "未命名实验")
                self._log(instance_id, "INFO", f"成功创建 ROMA 实验: {exp_name}, versionUid: {version_uid}")
                return version_uid

        except httpx.HTTPStatusError as e:
            error_msg = f"ROMA API 请求失败 (HTTP {e.response.status_code}): {e.response.text}"
            self._log(instance_id, "ERROR", error_msg)
            raise Exception(error_msg)
        except httpx.RequestError as e:
            error_msg = f"ROMA API 请求异常: {str(e)}"
            self._log(instance_id, "ERROR", error_msg)
            raise Exception(error_msg)
        except Exception as e:
            error_msg = f"创建 ROMA 实验失败: {str(e)}"
            self._log(instance_id, "ERROR", error_msg)
            raise

    def _submit_download_data(self, config: Dict[str, Any]) -> str:
        """
        提交数据下载任务到外部系统

        返回：
            str: 外部任务ID
        """
        # TODO: 调用外部API提交任务
        return f"task_{uuid.uuid4().hex[:8]}"

    def _submit_data_processing(self, config: Dict[str, Any]) -> str:
        """
        提交数据处理任务到外部系统

        返回：
            str: 外部任务ID
        """
        # TODO: 调用外部API提交任务
        return f"task_{uuid.uuid4().hex[:8]}"

    def _query_external_task_status(
        self,
        node_type: str,
        external_task_id: str
    ) -> Tuple[str, Dict[str, Any], str]:
        """
        查询外部任务状态

        Args:
            node_type: 节点类型 (如: create_experiment)
            external_task_id: 外部任务ID (对于ROMA，这是versionId)

        返回：
            (状态, 输出数据, 错误信息)
            状态: "running" | "completed" | "failed"
        """
        # 根据 node_type 调用不同的外部API
        if node_type == "create_experiment":
            if not env_FORBIDDEN_VISIT_ROMA:
                return self._query_roma_experiment_status(external_task_id)
        # 其他类型节点可以在这里扩展
        self._log(0, "WARNING", f"未知的节点类型: {node_type}, 使用模拟状态查询")
        # 默认返回completed避免阻塞
        from pathlib import Path
        template_path = Path(__file__).parent.parent / "templates/roma_example.yaml"
        with open(template_path, 'r', encoding='utf-8') as f:
          config = yaml.safe_load(f)
          config['versionInfo'] = config['config']
          del config['config']
        
        return "completed", config, ""

    def _query_roma_experiment_status(self, version_id: str) -> Tuple[str, Dict[str, Any], str]:
        """
        查询ROMA实验任务状态

        Args:
            version_id: ROMA训练作业版本ID

        Returns:
            (状态, 输出数据, 错误信息)
            状态: "running" | "completed" | "failed"
        """
        try:
            # 获取ROMA API Token
            token = self._reuse_or_fetch_token(
                account_name="BMC_API_USER",
                account_secret="AESGCM:2856B514BE721213DBEADA0A5F37BD15:57AA692302F16DE42F5914D82F45B8F94EA1635A9D72912911C19BD748F63CD6E33C34BDB2AFD98DDF67A45F1A084DF2E7DED0C3"
            )

            # 调用ROMA API查询训练作业状态
            url = "https://apigw.huawei.com/api/csb/roma-aistudio/train/job/info"
            headers = {
                "Content-Type": "application/json",
                "Authorization": token,
                "X-HW-ID": "com.huawei.ipd.noproduct.tenant_z00515901.BJ",
                "X-HW-APPKEY": "31y1UZJvij3YQ9QpZGk6dw=="
            }
            query_params = {"versionid": version_id}

            with httpx.Client(proxy=None, verify=False) as client:
                response = client.get(url, headers=headers, params=query_params, timeout=30.0)
                response.raise_for_status()
                data = response.json()

                # 检查API调用是否成功
                if not data.get("success"):
                    error_msg = data.get("msg", "ROMA API调用失败")
                    return "failed", {}, error_msg

                # 提取状态码
                version_info = data.get("versionInfo", {})
                status_code = version_info.get("statusCode", "MISS")

                # 映射ROMA状态码到工作流引擎状态
                workflow_status = self._map_roma_status_to_workflow(status_code)

                # 构建输出数据
                output_data = {
                    "versionId": version_id,
                    "romaStatusCode": status_code,
                    "versionInfo": version_info
                }

                # 构建错误信息
                error_msg = ""
                if workflow_status == "failed":
                    error_msg = f"ROMA训练作业失败，状态码: {status_code}"

                return workflow_status, output_data, error_msg

        except httpx.HTTPStatusError as e:
            error_msg = f"ROMA API HTTP错误: {e.response.status_code}"
            return "failed", {}, error_msg
        except httpx.TimeoutException:
            error_msg = "ROMA API调用超时，将继续重试"
            # 超时时返回running状态，继续轮询
            return "running", {}, error_msg
        except Exception as e:
            error_msg = f"查询ROMA任务状态异常: {str(e)}"
            # 发生异常时返回running状态，继续轮询而不是直接失败
            return "running", {}, error_msg

    def _map_roma_status_to_workflow(self, status_code: str) -> str:
        """
        映射ROMA状态码到工作流引擎状态

        ROMA状态码说明:
        - 已完成状态: '10', 'TERMINATED', 'success'
        - 运行中状态: '1', '2', '4', '7', '8', '000', '9', '15', '18', '19', '24', '26', 'RUNNING'
        - 失败状态: '3', '5', '6', '11', '13', '21', '23', '001', 'ERROR', 'fail'
        - 已停止状态: '12', '14', '25' (视为失败)
        - 未知状态: '0', 'MISS', 'miss' (继续轮询)

        Args:
            status_code: ROMA状态码

        Returns:
            "running" | "completed" | "failed"
        """
        # 已完成状态
        if status_code in ['10', 'TERMINATED', 'success']:
            return "completed"

        # 运行中状态 (包括初始化、创建中、提交中、排队中、运行中等)
        if status_code in ['1', '2', '4', '7', '8', '000', '9', '15', '18', '19', '24', '26', 'RUNNING']:
            return "running"

        # 失败状态
        if status_code in ['3', '5', '6', '11', '13', '21', '23', '001', 'ERROR', 'fail']:
            return "failed"

        # 已停止、已丢失、被抢占状态 - 视为失败
        if status_code in ['12', '14', '25']:
            return "failed"

        # 未知状态、状态缺失 - 继续轮询
        if status_code in ['0', 'MISS', 'miss']:
            return "running"

        # 其他未知状态码，继续轮询
        return "running"

    # ==================== 辅助方法 ====================

    def _get_current_node_execution(self, instance_id: int) -> Optional[WorkflowNodeExecution]:
        """获取当前正在执行的节点"""
        return self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.workflow_instance_id == instance_id,
            WorkflowNodeExecution.status.in_([
                self.NODE_STATUS_SUBMITTED,
                self.NODE_STATUS_RUNNING
            ])
        ).order_by(WorkflowNodeExecution.started_at.desc()).first()

    def _get_next_pending_node(self, instance_id: int, nodes: list) -> Optional[str]:
        """
        获取下一个待执行的节点ID

        逻辑：
        1. 检查是否有 pending 状态的节点
        2. 如果没有，根据工作流定义和已完成节点，确定下一个节点
        """
        # 查询所有节点执行记录
        executions = self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.workflow_instance_id == instance_id
        ).all()

        execution_map = {e.node_id: e for e in executions}

        if not execution_map:
            return nodes[0].get("id") if nodes else None

        # 找到第一个未执行或待执行的节点
        for index, node in enumerate(nodes):
            node_id = node.get("id")
            if node_id not in execution_map:
                # 未执行的节点 - 检查前置节点是否已完成
                if nodes[index - 1]["id"] in execution_map:
                    if execution_map[nodes[index - 1]["id"]].status == self.NODE_STATUS_COMPLETED:
                        return node_id
            elif execution_map[node_id].status in [self.NODE_STATUS_PENDING, self.NODE_STATUS_PREPARE]:
                # 待执行或准备中的节点
                return node_id

        return None

    def _all_nodes_completed(self, instance_id: int) -> bool:
        """检查是否所有节点都已完成"""
        pending_count = self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.workflow_instance_id == instance_id,
            WorkflowNodeExecution.status.in_([
                self.NODE_STATUS_PENDING,
                self.NODE_STATUS_PREPARE,
                self.NODE_STATUS_SUBMITTED,
                self.NODE_STATUS_RUNNING
            ])
        ).count()

        return pending_count == 0

    def _get_node_outputs(self, instance_id: int) -> Dict[str, Dict[str, Any]]:
        """获取所有已完成节点的输出，用于变量替换"""
        executions = self.db.query(WorkflowNodeExecution).filter(
            WorkflowNodeExecution.workflow_instance_id == instance_id,
            WorkflowNodeExecution.status == self.NODE_STATUS_COMPLETED
        ).all()

        outputs = {}
        for e in executions:
            if e.output_data:
                outputs[e.node_id] = json.loads(e.output_data)

        return outputs

    def _validate_variable_references(
        self,
        workflow_def: Dict[str, Any]
    ) -> Tuple[bool, List[str]]:
        """
        验证工作流中的变量引用是否合法

        检查项：
        1. 引用的节点是否存在
        2. JSONPath 语法是否正确
        3. 是否存在循环依赖

        Returns:
            (是否合法, 错误信息列表)
        """
        errors = []
        nodes = workflow_def.get("nodes", [])
        node_ids = {node["id"] for node in nodes}

        # 构建依赖图
        dependencies = {}

        for node in nodes:
            node_id = node["id"]
            config_str = json.dumps(node.get("config", {}))

            # 提取所有变量引用
            pattern = r'\{\{([^.}]+)\.([^}]+)\}\}'
            matches = re.findall(pattern, config_str)

            deps = set()
            for ref_node_id, path_expr in matches:
                # 跳过内置变量
                if ref_node_id in ["workflow", "timestamp"]:
                    continue

                # 检查引用的节点是否存在
                if ref_node_id not in node_ids:
                    errors.append(
                        f"节点 '{node_id}' 引用了不存在的节点 '{ref_node_id}'"
                    )
                    continue

                # 验证 JSONPath 语法
                try:
                    # 移除旧格式的 "output." 前缀
                    if path_expr.startswith("output."):
                        path_expr = path_expr[7:]

                    # 转换为标准 JSONPath
                    if not path_expr.startswith('$'):
                        path_expr = f"$.{path_expr}"

                    jsonpath_parse(path_expr)
                except JsonPathParserError as e:
                    errors.append(
                        f"节点 '{node_id}' 的变量引用语法错误: {{{{{ref_node_id}.{path_expr}}}}} - {str(e)}"
                    )

                deps.add(ref_node_id)

            dependencies[node_id] = deps

        # 检查循环依赖
        def has_cycle(node, visited, rec_stack):
            visited.add(node)
            rec_stack.add(node)

            for dep in dependencies.get(node, []):
                if dep not in visited:
                    if has_cycle(dep, visited, rec_stack):
                        return True
                elif dep in rec_stack:
                    return True

            rec_stack.remove(node)
            return False

        visited = set()
        for node_id in node_ids:
            if node_id not in visited:
                if has_cycle(node_id, visited, set()):
                    errors.append(f"检测到循环依赖，涉及节点: {node_id}")

        return len(errors) == 0, errors
